<?php
/**
 * WxYun Upload SDK
 * 
 * @copyright wanthings.com
 * @author lizf <lizaifang@gmail.com>
 * @version 0.4a
 */
//namespace WxYun;
class WxYun
{
    const TOP = 'top';
    const BOTTOM = 'bottom';
    const LEFT = 'left';
    const RIGHT = 'right';
    const MIDDLE = 'middle';
    public $errno;
    public $errmsg;
    public $debug = false;// debug flags

    private $_base_url = 'http://storage.wanthings.com/';
    private $_download_url = 'http://storage.wanthings.com/download/';
    private $_preview_url = 'http://storage.wanthings.com/preview/';
    private $_upload_url = 'api/file/';
    private $_filelist_url = 'api/filelist/';
    private $_fileinfo_url = 'api/fileinfo/';
    private $_chunk_url = 'api/file/chunk/';
    private $_chunks = array();
    //options['debug']
    //options['chunk_min_len']
    //options['chunk_len']

    /**
     * __construct description
     * @param string $user    username of wxyun
     * @param string $token   token of wxyun
     * @param array  $options some config, like debug mode, timeout blablabla
     */
    public function __construct($user, $token, $options = array())
    {
        $this->token = $token;
        $this->user = $user;
        if (isset($options['debug'])) $this->debug = $options['debug'];
    }

    private function getUploadUrl()
    {
        return $this->_base_url . $this->_upload_url;
    }

    private function getFileListUrl()
    {
        return $this->_base_url . $this->_filelist_url;
    }

    private function getFileInfoUrl()
    {
        return $this->_base_url . $this->_fileinfo_url;
    }

    private function getChunkUrl()
    {
        return $this->_base_url . $this->_chunk_url;
    }

    private function getHeaders($bucket)
    {
        $headers = array();
        // build headers for request
        $nonce = $this->getNonce();
        $headers[] = 'Signer: ' . $this->user;
        $headers[] = 'Nonce: ' . $nonce;
        $headers[] = 'Signature: ' . $this->sign(compact('nonce', 'bucket'));
        // $headers[] = 'Content-type: application/x-www-form-urlencoded;charset=UTF-8';
        $headers[] = 'Accept: application/json';
        if ($this->debug) var_dump($headers);

        return $headers;
    }

    /**
     * preUpload
     * 
     * @param  string $hash   file hash to upload
     * @param  string $name   file name to upload
     * @param  string $path   file path to upload
     * @param  string $bucket where put the file into
     * 
     * @return array          return fileinfo if file exists, otherwise return empty array;
     */
    public function preUpload($hash, $name, $path, $memo, $bucket = 'default')
    {
        $result = array();
        // build post data for request
        $data = array();
        $data['hash'] = $hash;
        $data['path'] = $path;
        $data['name'] = $name;
        $data['memo'] = $memo;
        if ($this->debug) var_dump($data);

        // build url for request
        $url = $this->getUploadUrl() . $this->buildUrl($bucket);
        if ($this->debug) var_dump($url);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (!curl_errno($ch)) {
            $info = curl_getinfo($ch);
            if ($this->debug) var_dump($info['http_code']);
            if ($this->debug) var_dump($response);
            if ($info['http_code'] == 200) {
                $result = json_decode($response, true);
            }
        }
        curl_close($ch);
        return $result;
    }

    /**
     * upload
     * 
     * @param  string $filename path of a file to upload
     * @param  string $name     filename of origin
     * @param  string $path     put
     * @param  string $bucket   where to put the file
     * 
     * @return array          return fileinfo if file exists, otherwise return empty array;
     */
    public function baseUpload($filename, $path = '', $name = '', $memo = '', $bucket = 'default')
    {
        $result = array();
        $pathinfo = pathinfo($filename);
        $hash = md5_file($filename);

        $data = array();
        $data['hash'] = $hash;
        $data['file'] = $this->getCurlFile($filename, $name);
        $data['path'] = !empty($path) ? $path : $pathinfo['basename'];
        $data['name'] = !empty($name) ? $name : $pathinfo['basename'];
        $data['memo'] = $memo;
        if ($this->debug) var_dump($data);

        $url = $this->getUploadUrl() . $this->buildUrl($bucket);
        if ($this->debug) var_dump($url);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (!curl_errno($ch)) {
            $info = curl_getinfo($ch);
            if ($this->debug) var_dump($info['http_code']);
            if ($this->debug) var_dump($response);
            if ($info['http_code'] == 200) {
                $result = json_decode($response, true);
            }
        }
        curl_close($ch);
        return $result;
    }

    /**
     * upload
     * 
     * @param  string $filename path of a file to upload
     * @param  string $name     filename of origin
     * @param  string $path     put
     * @param  string $bucket   where to put the file
     * 
     * @return array          return fileinfo if file exists, otherwise return empty array;
     */
    public function upload($filename, $path = '', $name = '', $memo = '', $bucket = 'default')
    {
        $result = array();
        $pathinfo = pathinfo($filename);
        $hash = md5_file($filename);
        $pre_result = $this->preUpload($hash, $name, $path, $memo, $bucket);
        if (!empty($pre_result)) {
            if ($this->debug) var_dump('file pre upload success');
            $result = $pre_result;
        } else {
            $data = array();
            $data['hash'] = $hash;
            $data['file'] = $this->getCurlFile($filename, $name);
            // fixed dirname but maybe has bug for windows, like C:\\
            $data['path'] = !empty($path) ? $path : $pathinfo['dirname'];
            $data['name'] = !empty($name) ? $name : $pathinfo['basename'];
            $data['memo'] = $memo;
            if ($this->debug) var_dump($data);

            $url = $this->getUploadUrl() . $this->buildUrl($bucket);
            if ($this->debug) var_dump($url);

            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $response = curl_exec($ch);
            if (!curl_errno($ch)) {
                $info = curl_getinfo($ch);
                if ($this->debug) var_dump($info['http_code']);
                if ($this->debug) var_dump($response);
                if ($info['http_code'] == 200) {
                    $result = json_decode($response, true);
                }
            }
            curl_close($ch);
        }
        return $result;
    }

    /**
     * delete a file by key
     * 
     * @param  string $key    the file key to delete
     * @param  string $bucket where the file store in
     * 
     * @return boolean         return true if delete success, otherwise false;
     */
    public function delete($key, $bucket = 'default')
    {
        $result = false;
        $url = $this->getUploadUrl($bucket) . $this->buildUrl($bucket, $key);
        if ($this->debug) var_dump($url);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (!curl_errno($ch)) {
            $info = curl_getinfo($ch);
            if ($this->debug) var_dump($info['http_code']);
            if ($info['http_code'] == 200) {
                $result = true;
            }
        }
        curl_close($ch);
        return $result;
    }

    /**
     * get file list of a bucket
     *
     * @param  string $path   which path to display
     * @param  string $bucket where the path in
     * 
     * @return array         file list array
     */
    public function filelist($path = '/', $bucket = 'default')
    {
        $result = array();

        $url = $this->getFileListUrl() . $this->buildUrl($bucket);
        if ($this->debug) var_dump($url);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (!curl_errno($ch)) {
            $info = curl_getinfo($ch);
            if ($this->debug) var_dump($info['http_code']);
            if ($info['http_code'] == 200) {
                $result = json_decode($response, true);
            }
        }
        curl_close($ch);
        return $result;
    }

    /**
     * get fileinfo of a key from bucket
     * 
     * @param  string $bucket bucket name
     * @param  string $key    key of a file
     * 
     * @return array         fileinfo
     */
    public function fileinfo($key, $bucket = 'default')
    {
        $result = array();

        $url = $this->getFileInfoUrl($bucket) . $this->buildUrl($bucket, $key);
        if ($this->debug) print_r($url);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (!curl_errno($ch)) {
            $info = curl_getinfo($ch);
            if ($this->debug) var_dump($info['http_code']);
            if ($info['http_code'] == 200) {
                $result = json_decode($response, true);
            }
        }
        curl_close($ch);
        return $result;
    }

    /**
     * use small chunk for Upload
     * 
     * @param  string $chunk chunk file path
     * @return string        [description]
     */
    public function chunkFileUpload($chunk)
    {
        $result = array();
        $hash = md5_file($chunk);

        //FIXED ME
        $data['chunk'] = $this->getCurlFile($chunk, '');
        $data['hash'] = $hash;

        $url = $this->getChunkUrl();
        if ($this->debug) print_r($url);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (!curl_errno($ch)) {
            $info = curl_getinfo($ch);
            if ($this->debug) var_dump($info['http_code']);
            if ($info['http_code'] == 200) {
                $result = json_decode($response, true);
            }
        }
        curl_close($ch);
        return $result;
    }

    /**
     * use small chunk for Upload
     * 
     * @param  string $chunk chunk file path
     * @return string        [description]
     */
    public function chunkUpload($chunk)
    {
        $result = array();
        $hash = md5_file($chunk);

        //FIXED ME
        $data['chunk'] = $this->getCurlFile($chunk, '');
        $data['hash'] = $hash;

        $url = $this->getChunkUrl();
        if ($this->debug) print_r($url);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (!curl_errno($ch)) {
            $info = curl_getinfo($ch);
            if ($this->debug) var_dump($info['http_code']);
            if ($info['http_code'] == 200) {
                $result = json_decode($response, true);
            }
        }
        curl_close($ch);
        return $result;
    }

    public function chunkUploadEnd($hash, $name, $path, $chunk, $memo, $bucket = 'default')
    {
        $data = array();
        $data['hash'] = $hash;
        $data['path'] = $path;
        $data['name'] = $name;
        $data['chunk'] = $chunk;
        $data['memo'] = $memo;
        if ($this->debug) var_dump($data);

        $url = $this->getUploadUrl() . $this->buildUrl($bucket);
        if ($this->debug) var_dump($url);

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders($bucket));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (!curl_errno($ch)) {
            $info = curl_getinfo($ch);
            if ($this->debug) var_dump($info['http_code']);
            if ($info['http_code'] == 200) {
                $result = json_decode($response, true);
            }
        }
        curl_close($ch);
        return $result;
    }

    /**
     * rangeUpload to upload split in small chunk
     * 
     * @param  string $filename file path
     * @param  string $path     upload into this path
     * @param  string $name     remote name after upload
     * @param  string $memo     just memo
     * @param  string $bucket   where the file store
     * @return array            fileinfo or empty array
     */
    public function rangeUpload($filename, $path = '', $name = '', $memo = '', $bucket = 'default')
    {
        $chunks = array();
        $this->tmp_dir = sys_get_temp_dir() . '/';
        $this->file_tmp = uniqid();
        $this->chunklen = 100000;// default chunk length = 100KB;
        $hash = md5_file($filename);
        $fp = fopen($filename, 'rb');
        $idx = 0;
        while (!feof($fp)) {
            /*
            $tmpfname = tempnam(sys_get_temp_dir(), uniqid());
            $fp = fopen($tmpfname, 'w');
            fwrite($fp, 'writing to tempfile');
            fclose($fp);
            unlink($fp);
            */
            $chunk = fread($fp, $chunklen);
            if ($this->debug) echo $idx++ . PHP_EOL;
            file_put_contents($this->tmp_dir . $this->file_tmp . '_' . $idx, $chunk);
            $response = $this->chunkFileUpload($this->tmp_dir . $this->file_tmp . '_' . $idx);
            $chunks[] = $response['hash'];
            /*
            $temp = tmpfile();
            fwrite($temp, 'writing to tempfile');
            fseek($temp, 0);
            echo fread($temp, 1024);
            fclose($temp);
            */
        }
        fclose($fp);
        // TODO: delete temp files;
        $result = $this->chunkUploadEnd($hash, $path, $name, $chunks, $memo, $bucket);
        return $result;
    }

    public function buildUrl()
    {
        $args = func_get_args();
        if ($this->debug ) var_dump(join('_', $args));
        array_unshift($args, $this->user);
        return join('_', $args);
    }

    public function getNonce()
    {
        return uniqid();
    }

    /**
     * calulate signature and params for request
     * 
     * @param  string $nonce random string for request
     * 
     * @return string        signature of nonce
     */
    private function sign($params)
    {
        $tmpArr = array($this->user, $this->token, $params['nonce']);
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $signature = sha1($tmpStr);
        return $signature;
    }

    /**
     * verify signature and params from remote
     * 
     * @param  string  $sign   signature from remote server
     * @param  string  $params params from remote server
     * 
     * @return boolean         if the signature right
     */
    private function verify($sign, $params)
    {
        $signature = $this->sign($params['nonce']);
        if ($signature == $sign) {
            return true;
        } else {
            return false;
        }
    }

    public function getCurlFile($filename, $name, $mimetype=null)
    {
        clearstatcache();
        $size = filesize($filename);
        if (function_exists('finfo_open')) {
            $minfo = finfo_open(FILEINFO_MIME_TYPE);
            $mimetype = finfo_file($minfo, $filename);
            finfo_close($minfo);
        } else if (function_exists('mime_content_type')) {
            // FIXME php 5.2.14
            $mimetype = mime_content_type($filename);
        } else {
            $mimetype = '';
        }

        if (version_compare(phpversion(), '5.5', '<')) {
            $cfile = "@$filename;filename=". (!empty($name) ? $name : basename($filename)). ($mimetype ? ";type=$mimetype" : '');
        } else {
            $cfile = curl_file_create($filename, $mimetype, $name);
        }
        return $cfile;
    }

    private function getMaxUploadSize()
    {
        //post_max_size
        //upload_max_filesize
        //memory_limit
        //三者最小的
        return ini_get('upload_max_filesize');
    }

    public function getDownloadUrl($key, $bucket)
    {
        $url = $this->_download_url . $this->buildUrl($bucket, $key);
        return $url;
    }

    public function getPreviewUrl($key, $bucket, $ext='png', $w='', $h='', $c='')
    {
        $params = array();
        if (!empty($w)) $params[] = "w-$w";
        if (!empty($h)) $params[] = "h-$h";
        if (!empty($c)) $params[] = "c-$c";
        if (is_null($ext)) $ext = 'png';
        if (!empty($params)) {
            $url = $this->_preview_url . $this->buildUrl($bucket, $key) . '_' . join('_', $params) . '.' . $ext;
        } else {
            $url = $this->_preview_url . $this->buildUrl($bucket, $key) . '.' . $ext;
        }
        return $url;
    }
}
